package studio.baka.satoripixeldungeon.items;

import studio.baka.satoripixeldungeon.Assets;
import studio.baka.satoripixeldungeon.Dungeon;
import studio.baka.satoripixeldungeon.actors.buffs.Buff;
import studio.baka.satoripixeldungeon.actors.buffs.Burning;
import studio.baka.satoripixeldungeon.actors.buffs.Frost;
import studio.baka.satoripixeldungeon.actors.hero.Hero;
import studio.baka.satoripixeldungeon.actors.mobs.Mimic;
import studio.baka.satoripixeldungeon.actors.mobs.Wraith;
import studio.baka.satoripixeldungeon.effects.CellEmitter;
import studio.baka.satoripixeldungeon.effects.Flare;
import studio.baka.satoripixeldungeon.effects.Speck;
import studio.baka.satoripixeldungeon.effects.particles.ElmoParticle;
import studio.baka.satoripixeldungeon.effects.particles.FlameParticle;
import studio.baka.satoripixeldungeon.effects.particles.ShadowParticle;
import studio.baka.satoripixeldungeon.items.armor.Armor;
import studio.baka.satoripixeldungeon.items.artifacts.Artifact;
import studio.baka.satoripixeldungeon.items.bombs.Bomb;
import studio.baka.satoripixeldungeon.items.food.ChargrilledMeat;
import studio.baka.satoripixeldungeon.items.food.FrozenCarpaccio;
import studio.baka.satoripixeldungeon.items.food.MysteryMeat;
import studio.baka.satoripixeldungeon.items.journal.DocumentPage;
import studio.baka.satoripixeldungeon.items.potions.Potion;
import studio.baka.satoripixeldungeon.items.potions.PotionOfStrength;
import studio.baka.satoripixeldungeon.items.rings.RingOfWealth;
import studio.baka.satoripixeldungeon.items.scrolls.Scroll;
import studio.baka.satoripixeldungeon.items.scrolls.ScrollOfUpgrade;
import studio.baka.satoripixeldungeon.items.wands.Wand;
import studio.baka.satoripixeldungeon.messages.Messages;
import studio.baka.satoripixeldungeon.sprites.ItemSprite;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundlable;
import com.watabou.utils.Bundle;
import com.watabou.utils.Random;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

public class Heap implements Bundlable {

    public enum Type {
        HEAP,
        FOR_SALE,
        CHEST,
        LOCKED_CHEST,
        CRYSTAL_CHEST,
        TOMB,
        SKELETON,
        REMAINS,
        MIMIC
    }

    public Type type = Type.HEAP;

    public int pos = 0;

    public ItemSprite sprite;
    public boolean seen = false;
    public boolean haunted = false;

    public LinkedList<Item> items = new LinkedList<>();

    public void open(Hero hero) {
        switch (type) {
            case MIMIC:
                if (Mimic.spawnAt(pos, items) != null) {
                    destroy();
                } else {
                    type = Type.CHEST;
                }
                break;
            case TOMB:
                Wraith.spawnAround(hero.pos);
                break;
            case REMAINS:
            case SKELETON:
                CellEmitter.center(pos).start(Speck.factory(Speck.RATTLE), 0.1f, 3);
                break;
            default:
        }

        if (haunted) {
            if (Wraith.spawnAt(pos) == null) {
                hero.sprite.emitter().burst(ShadowParticle.CURSE, 6);
                hero.damage(hero.HP / 2, this);
            }
            Sample.INSTANCE.play(Assets.SND_CURSED);
        }

        if (type != Type.MIMIC) {
            type = Type.HEAP;
            ArrayList<Item> bonus = RingOfWealth.tryForBonusDrop(hero, 1);
            if (bonus != null && !bonus.isEmpty()) {
                items.addAll(0, bonus);
                if (RingOfWealth.latestDropWasRare) {
                    new Flare(8, 48).color(0xAA00FF, true).show(sprite, 2f);
                    RingOfWealth.latestDropWasRare = false;
                } else {
                    new Flare(8, 24).color(0xFFFFFF, true).show(sprite, 2f);
                }
            }
            sprite.link();
            sprite.drop();
        }
    }

    public Heap setHauntedIfCursed(float chance) {
        for (Item item : items) {
            if (item.cursed && Random.Float() < chance) {
                haunted = true;
                item.cursedKnown = true;
                break;
            }
        }
        return this;
    }

    public int size() {
        return items.size();
    }

    public Item pickUp() {

        if (items.isEmpty()) {
            destroy();
            return null;
        }
        Item item = items.removeFirst();
        if (items.isEmpty()) {
            destroy();
        } else if (sprite != null) {
            sprite.view(this).place(pos);
        }

        return item;
    }

    public Item peek() {
        return items.peek();
    }

    public void drop(Item item) {

        if (item.stackable && type != Type.FOR_SALE) {

            for (Item i : items) {
                if (i.isSimilar(item)) {
                    item = i.merge(item);
                    break;
                }
            }
            items.remove(item);

        }

        if (item.dropsDownHeap && type != Type.FOR_SALE) {
            items.add(item);
        } else {
            items.addFirst(item);
        }

        if (sprite != null) {
            sprite.view(this).place(pos);
        }
    }

    public void replace(Item a, Item b) {
        int index = items.indexOf(a);
        if (index != -1) {
            items.remove(index);
            items.add(index, b);
        }
    }

    public void remove(Item a) {
        items.remove(a);
        if (items.isEmpty()) {
            destroy();
        } else if (sprite != null) {
            sprite.view(this).place(pos);
        }
    }

    public void burn() {

        if (type == Type.MIMIC) {
            Mimic m = Mimic.spawnAt(pos, items);
            if (m != null) {
                Buff.affect(m, Burning.class).reignite(m);
                m.sprite.emitter().burst(FlameParticle.FACTORY, 5);
                destroy();
            }
        }

        if (type != Type.HEAP) {
            return;
        }

        boolean burnt = false;
        boolean evaporated = false;

        for (Item item : items.toArray(new Item[0])) {
            if (item instanceof Scroll
                    && !(item instanceof ScrollOfUpgrade)) {
                items.remove(item);
                burnt = true;
            } else if (item instanceof Dewdrop) {
                items.remove(item);
                evaporated = true;
            } else if (item instanceof MysteryMeat) {
                replace(item, ChargrilledMeat.cook((MysteryMeat) item));
                burnt = true;
            } else if (item instanceof Bomb) {
                items.remove(item);
                ((Bomb) item).explode(pos);
                if (((Bomb) item).explodesDestructively()) {
                    //stop processing the burning, it will be replaced by the explosion.
                    return;
                } else {
                    burnt = true;
                }
            }
        }

        if (burnt || evaporated) {

            if (Dungeon.level.heroFOV[pos]) {
                if (burnt) {
                    burnFX(pos);
                } else {
                    evaporateFX(pos);
                }
            }

            if (isEmpty()) {
                destroy();
            } else if (sprite != null) {
                sprite.view(this).place(pos);
            }

        }
    }

    //Note: should not be called to initiate an explosion, but rather by an explosion that is happening.
    public void explode() {

        //breaks open most standard containers, mimics die.
        if (type == Type.MIMIC || type == Type.CHEST || type == Type.SKELETON) {
            type = Type.HEAP;
            sprite.link();
            sprite.drop();
            return;
        }

        if (type == Type.HEAP) {
            for (Item item : items.toArray(new Item[0])) {
                if (item instanceof Potion) {
                    items.remove(item);
                    ((Potion) item).shatter(pos);

                } else if (item instanceof Bomb) {
                    items.remove(item);
                    ((Bomb) item).explode(pos);
                    if (((Bomb) item).explodesDestructively()) {
                        //stop processing current explosion, it will be replaced by the new one.
                        return;
                    }

                    //unique and upgraded items can endure the blast
                } else if (!(item.level() > 0 || item.unique
                        || (item instanceof Armor && ((Armor) item).checkSeal() != null)))
                    items.remove(item);
            }

            if (isEmpty()) {
                destroy();
            } else if (sprite != null) {
                sprite.view(this).place(pos);
            }
        }
    }

    public void freeze() {
        if (type == Type.MIMIC) {
            Mimic m = Mimic.spawnAt(pos, items);
            if (m != null) {
                Buff.prolong(m, Frost.class, Frost.duration(m) * Random.Float(1.0f, 1.5f));
                destroy();
            }
        }

        if (type != Type.HEAP) {
            return;
        }

        boolean frozen = false;
        for (Item item : items.toArray(new Item[0])) {
            if (item instanceof MysteryMeat) {
                replace(item, FrozenCarpaccio.cook((MysteryMeat) item));
                frozen = true;
            } else if (item instanceof Potion && !(item instanceof PotionOfStrength)) {
                items.remove(item);
                ((Potion) item).shatter(pos);
                frozen = true;
            } else if (item instanceof Bomb) {
                ((Bomb) item).fuse = null;
                frozen = true;
            }
        }

        if (frozen) {
            if (isEmpty()) {
                destroy();
            } else if (sprite != null) {
                sprite.view(this).place(pos);
            }
        }
    }

    public static void burnFX(int pos) {
        CellEmitter.get(pos).burst(ElmoParticle.FACTORY, 6);
        Sample.INSTANCE.play(Assets.SND_BURNING);
    }

    public static void evaporateFX(int pos) {
        CellEmitter.get(pos).burst(Speck.factory(Speck.STEAM), 5);
    }

    public boolean isEmpty() {
        return items == null || items.size() == 0;
    }

    public void destroy() {
        Dungeon.level.heaps.remove(this.pos);
        if (sprite != null) {
            sprite.kill();
        }
        items.clear();
    }

    @Override
    public String toString() {
        switch (type) {
            case CHEST:
            case MIMIC:
                return Messages.get(this, "chest");
            case LOCKED_CHEST:
                return Messages.get(this, "locked_chest");
            case CRYSTAL_CHEST:
                return Messages.get(this, "crystal_chest");
            case TOMB:
                return Messages.get(this, "tomb");
            case SKELETON:
                return Messages.get(this, "skeleton");
            case REMAINS:
                return Messages.get(this, "remains");
            default:
                return peek().toString();
        }
    }

    public String info() {
        switch (type) {
            case CHEST:
            case MIMIC:
                return Messages.get(this, "chest_desc");
            case LOCKED_CHEST:
                return Messages.get(this, "locked_chest_desc");
            case CRYSTAL_CHEST:
                if (peek() instanceof Artifact)
                    return Messages.get(this, "crystal_chest_desc", Messages.get(this, "artifact"));
                else if (peek() instanceof Wand)
                    return Messages.get(this, "crystal_chest_desc", Messages.get(this, "wand"));
                else
                    return Messages.get(this, "crystal_chest_desc", Messages.get(this, "ring"));
            case TOMB:
                return Messages.get(this, "tomb_desc");
            case SKELETON:
                return Messages.get(this, "skeleton_desc");
            case REMAINS:
                return Messages.get(this, "remains_desc");
            default:
                return peek().info();
        }
    }

    private static final String POS = "pos";
    private static final String SEEN = "seen";
    private static final String TYPE = "type";
    private static final String ITEMS = "items";
    private static final String HAUNTED = "haunted";

    @SuppressWarnings("unchecked")
    @Override
    public void restoreFromBundle(Bundle bundle) {
        pos = bundle.getInt(POS);
        seen = bundle.getBoolean(SEEN);
        type = Type.valueOf(bundle.getString(TYPE));

        items = new LinkedList<>((Collection<Item>) ((Collection<?>) bundle.getCollection(ITEMS)));
        items.removeAll(Collections.singleton(null));

        //remove any document pages that either don't exist anymore or that the player already has
        items.removeIf(item -> item instanceof DocumentPage
                && (!((DocumentPage) item).document().pages().contains(((DocumentPage) item).page())
                || ((DocumentPage) item).document().hasPage(((DocumentPage) item).page())));

        haunted = bundle.getBoolean(HAUNTED);

    }

    @Override
    public void storeInBundle(Bundle bundle) {
        bundle.put(POS, pos);
        bundle.put(SEEN, seen);
        bundle.put(TYPE, type.toString());
        bundle.put(ITEMS, items);
        bundle.put(HAUNTED, haunted);
    }

}
